﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using RoverStudio.Common.WorldModel;
using RoverStudio.Math;
using System.Drawing.Drawing2D;

namespace RoverStudio.MapDisplay.WM2D
{
    public partial class MapDisplay2DControl : Control
    {

        //TODO: LACI figyelj arra, hogy ez az érték ugyanaz legyen mint CirclePositionSource-ban levő
        private double pixelMeter = 0.02;

        //TODO: Laci állítsd be a hátteret itt
        private Bitmap backgroundImage = null; 

        private Dictionary<object, object> objectsProperties =
            new Dictionary<object, object>();

        public Dictionary<object, object> ObjectsProperties
        {
            get { return objectsProperties; }
        }


        private Vector2D origin;

        /// <summary>
        /// Gets and sets the origin of the map in screen coordinates.
        /// </summary>
        public Vector2D Origin
        {
            get { return origin; }
            set
            {
                origin = value;
                this.Invalidate();
            }
        }

        public void MoveOrigin(Vector2D move)
        {
            this.origin = this.origin - new Vector2D(move.X - this.Width / 2.0, move.Y - this.Height / 2.0);
            this.Invalidate();
        }

        private double scale;

        public new double Scale
        {
            get { return scale; }
            set { scale = value; }
        }

        public event MapUserInteractionEventHandler MapUserInteraction;

        private Brush defaultBackgroundColor = Brushes.SandyBrown;
        private Brush defaultObjectColor = Brushes.Black;

        protected WorldModel worldModel;

        public WorldModel WorldModel
        {
            get
            {
                return this.worldModel;
            }
            set
            {
                if (worldModel != null)
                {
                    worldModel.WorldChanged -= new WorldChangedEventHandler(UpdateWorld);
                }
                this.worldModel = value;
                if (worldModel != null)
                {
                    worldModel.WorldChanged += new WorldChangedEventHandler(UpdateWorld);
                }
            }
        }

        private void UpdateWorld(object sender, WorldChangedEventArgs wcea)
        {
            this.Invalidate();
        }

        public MapDisplay2DControl()
        {
            InitializeComponent();


            //Double buffering
            this.SetStyle(ControlStyles.Opaque, true);
            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);


        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            if (worldModel == null)
                return;

            Graphics gr = pe.Graphics;

            //Clear the background
            if ((objectsProperties["Background color"] is Color))
                gr.FillRectangle(new SolidBrush((Color)objectsProperties["Background color"]), 0, 0, this.Width, this.Height);
            else
                gr.FillRectangle(defaultBackgroundColor, 0, 0, this.Width, this.Height);

            //TODO: backgroundImage
            if (this.backgroundImage != null)
            {
                Vector2D topLeft = TransformWorldCoordinatesToScreen(
                    new Vector3D(-this.backgroundImage.Width / 2.0 * this.pixelMeter,
                    this.backgroundImage.Height / 2.0 * this.pixelMeter,
                    0.0));
                Vector2D bottomRight = TransformWorldCoordinatesToScreen(
                    new Vector3D(this.backgroundImage.Width / 2.0 * this.pixelMeter,
                    -this.backgroundImage.Height / 2.0 * this.pixelMeter,
                    0.0));
                Vector2D diameter = bottomRight - topLeft;

                gr.DrawImage(this.backgroundImage, (float)topLeft.X, (float)topLeft.Y, (float)diameter.X, (float)diameter.Y);

            }
            //TODO: Laci kommentezd ki, ha nem kell
            DrawGrid(0.25, gr);

            List<PhysicalObject> physicalObjects = worldModel.GetPhysicalObjects();
            List<CreatedObject> createdObjects = worldModel.GetCreatedObjects();

            foreach (PhysicalObject po in physicalObjects)
            {
                if (po is CaterpillarVehicle)
                    DrawCaterpillarVehicle((CaterpillarVehicle)po, gr);
                else
                    DrawPhysicalObject(po, gr);
            }

            foreach (CreatedObject co in createdObjects)
            {
                if (co is Path)
                    DrawPath((Path)co, gr);
                else
                    DrawCreatedObject(co, gr);
            }

            //Call parent's OnPaint
            base.OnPaint(pe);
        }

        private void DrawGrid(double width, Graphics gr)
        {
            int widthOnScreen =
                (int)((TransformWorldCoordinatesToScreen(new Vector3D(width, 0.0, 0.0)) - TransformWorldCoordinatesToScreen(new Vector3D(0.0, 0.0, 0.0)))
            .X);

            if (widthOnScreen < 5)
                return;
            int x = this.Width / 2;
            int y = this.Height / 2;

            while (x < this.Width)
            {
                gr.DrawLine(Pens.White, x, 0, x, this.Height);
                gr.DrawLine(Pens.White, this.Width - x, 0, this.Width - x, this.Height);
                x += widthOnScreen;
            }
            while (y < this.Height)
            {
                gr.DrawLine(Pens.White, 0, y, this.Width, y);
                gr.DrawLine(Pens.White, 0, this.Height - y, this.Width, this.Height - y);
                y += widthOnScreen;
            }

        }

        private void DrawPath(Path path, Graphics gr)
        {
            if (path.PathElements.Count > 0)
            {
                List<PointF> points = new List<PointF>();
                for (int i = 0; i < path.PathElements.Count; i++)
                {
                    Vector2D screenPoint = TransformWorldCoordinatesToScreen(path.PathElements[i]);
                    PointF point = new PointF((float)screenPoint.X, (float)screenPoint.Y);
                    points.Add(point);
                    gr.FillEllipse(Brushes.Red, point.X - 2.0F, point.Y - 2.0F, 4.0F, 4.0F);

    
                    if (i == path.SelectedPathElementIndex)
                    {
                        gr.FillEllipse(Brushes.Blue, point.X - 6.0F, point.Y - 6.0F, 12.0F, 12.0F);
                    }
                }

                if (path.PathElements.Count > 1)
                {
                    if (objectsProperties[path.Rover] != null)
                        gr.DrawLines(new Pen((Color)objectsProperties[path.Rover], 2.0F), points.ToArray());
                    else
                        gr.DrawLines(new Pen(defaultObjectColor, 2.0F), points.ToArray());
                }
            }
        }


        private void DrawCaterpillarVehicle(CaterpillarVehicle cv, Graphics gr)
        {
            //TODO: implement better
            Vector2D position = TransformWorldCoordinatesToScreen(cv.Position);
            if (cv.RoverImage != null)
            {
                float maxSize = (float)System.Math.Sqrt(2) * (System.Math.Max((float)cv.Diameter.X, (float)cv.Diameter.Y));
                //If image is too small, we scale it to appear.
                float scale = 400.0F / maxSize;

                Vector2D y2d = ((Vector2D)cv.Orientation.YAxis).GetNormalizedVector2D();

                float x11 = (float)y2d.Y;
                float x12 = (float)y2d.X;
                float x21 = (float)-y2d.X;
                float x22 = (float)y2d.Y;

                Matrix rotationMatrix = new Matrix(
                    x11 * scale, x12 * scale, x21 * scale, x22 * scale, 0.0F, 0.0F);

                rotationMatrix.Translate(
                    scale * ((float)maxSize / 2.0F - (float)cv.Diameter.X / 2.0F * x11 - (float)cv.Diameter.Y / 2.0F * x21),
                    scale * (((float)maxSize / 2.0F - (float)cv.Diameter.X / 2.0F * x12) - (float)cv.Diameter.Y / 2.0F * x22),
                MatrixOrder.Append);
                Bitmap roverImage = new Bitmap((int)(maxSize * scale), (int)(maxSize * scale));
                Graphics g = Graphics.FromImage(roverImage);
                g.FillRectangle(Brushes.Pink, 0, 0, maxSize * scale, maxSize * scale);
                g.Transform = rotationMatrix;
                g.DrawImage(
                    cv.RoverImage,
                    new RectangleF(0.0F, 0.0F,
                    (float)cv.Diameter.X, (float)cv.Diameter.Y));
                roverImage.MakeTransparent(Color.Pink);
                gr.DrawImage(roverImage,
     (float)position.X - maxSize / 2.0F * (float)this.scale,
     (float)position.Y - maxSize / 2.0F * (float)this.scale,
     maxSize * (float)this.scale, maxSize * (float)this.scale);

            }
            else
            {
                if (objectsProperties[cv] != null)
                    gr.FillRectangle(new SolidBrush((Color)objectsProperties[cv]), (float)position.X - 5.0F, (float)position.Y - 5.0F, 10, 10);
                else
                    gr.FillRectangle(defaultObjectColor, (float)position.X - 5.0F, (float)position.Y - 5.0F, 10, 10);
                Vector2D dir = ((Vector2D)cv.Orientation.YAxis).FlipHorizontal();
                gr.DrawLine(new Pen(Color.White, 2.0F), (float)position.X, (float)position.Y, (float)(position.X + dir.X * 7), (float)(position.Y + dir.Y * 7));
            }
        }

        private void DrawPhysicalObject(PhysicalObject po, Graphics gr)
        {
            //TODO: implement better
            Vector2D position = TransformWorldCoordinatesToScreen(po.Position);
            gr.DrawRectangle(Pens.Red, (float)position.X - 5.0F, (float)position.Y - 5.0F, 10, 10);
        }

        private void DrawCreatedObject(CreatedObject co, Graphics gr)
        {
            //TODO: implement better
            //			Vector2D position = TransformWorldCoordinatesToScreen(co.Position);
            //			gr.DrawRectangle(Pens.Black, (float)position.X, (float) position.Y, 10,10);

        }


        public virtual Vector2D TransformWorldCoordinatesToScreen(Vector3D worldVector)
        {
            //TODO: implement for another world models

            //Vector2D screenOrigin = new Vector2D((double)this.Width / 2.0, (double)this.Height / 2.0);

            //If there is no worldModel, throw exception.
            if (worldModel == null)
                throw new Exception("MapControl does not have a World Model");

            //Y axis is inverted on the screen.    
            return new Vector2D((worldVector.X - worldModel.Origin.X) * scale, -(worldVector.Y - worldModel.Origin.Y) * scale).Shift(origin);
        }

        public virtual Vector3D TransformScreenCoordinatesToWorld(Vector2D screenVector)
        {
            //TODO: implement for another world models

            //Vector2D screenOrigin = new Vector2D((double)this.Width / 2.0, (double)this.Height / 2.0);

            //If there is no worldModel throw exception.
            if (worldModel == null)
                throw new Exception("MapControl does not have a World Model");

            //Y axis is inverted on the screen.    
            return new Vector3D((screenVector.X - origin.X) / scale, -(screenVector.Y - origin.Y) / scale, 0.0).Shift(worldModel.Origin);
        }



        private void MapDisplay2DControl_Click(object sender, EventArgs e)
        {
            MouseEventArgs me = (MouseEventArgs)e;
            EventType type = EventType.MouseBtnLeft;
            if (MouseButtons.Left == me.Button)
            {
                type = EventType.MouseBtnLeft;
            }
            else if (MouseButtons.Right == me.Button)
            {
                type = EventType.MouseBtnRight;
            }
            Vector2D screenVector = new Vector2D(me.X, me.Y);
            MapUserInteractionEventArgs muie =
                new MapUserInteractionEventArgs(type,
                                                screenVector,
                                                TransformScreenCoordinatesToWorld(screenVector),
                                                new Vector3D(0.0, 0.0, 1.0));

            if (MapUserInteraction != null)
            {
                MapUserInteraction(this, muie);
            }

        }

        private void MapDisplay2DControl_MouseMove(object sender, MouseEventArgs e)
        {
            MouseEventArgs me = (MouseEventArgs)e;
            EventType type = EventType.MouseMovement;

            Vector2D screenVector = new Vector2D(me.X, me.Y);
            MapUserInteractionEventArgs muie =
                new MapUserInteractionEventArgs(type,
                                                screenVector,
                                                TransformScreenCoordinatesToWorld(screenVector),
                                                new Vector3D(0.0, 0.0, 1.0));

            if (MapUserInteraction != null)
            {
                MapUserInteraction(this, muie);
            }
        }
    }
}
